//Aaron Hill, Eric Rotaru, Dan Newman
//Assigment 2
//Reader.c

/**TODO: 
1) 'remove' any empty lines so that the num of ss's and the ss's start at the top
of the file and form a block with no empty lines. ss.c assumes this when searching
for the ss.
2) make sure that if the ssNum is more than 1 digit, it doesn't mess things up
  */

#include "awget.h"

static int debug = 0;

FILE* getFile(char* fileName);

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {  // if the type is IPV4
        return &(((struct sockaddr_in*)sa)->sin_addr); //return this
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr); // (Implied) ELSE: its Ipv6
}

/**
	opens up a TCP socket and connects to the stepping stones specified in 
*/
void requestFile(char * port, char * url, FILE * chainfile){
	int sockfd;
	//TODO: make sure that any argument order is accepted
	//will contain the extracted information of the first stepping stone, ie. its ip and port.
	struct ipPortPair *firstSS = (struct ipPortPair *) malloc (sizeof(struct ipPortPair));
  
	
	
	struct timeval tv; //struct holding microseconds and seconds for timeout with select
	tv.tv_sec = 4; //seconds
	tv.tv_usec = 0; //microseconds
	fd_set readset; //file descriptor ready to read
    
	int bytes_sent;
	
	
	packLength filesize;
	//find the size of the file and put in filesize
	fseek( chainfile, 0L, SEEK_END);
	filesize = (packLength)ftell(chainfile);
	
	//put file marker at beginning of file
	fseek(chainfile, 0L, SEEK_SET);
	
	//find the size of the url
	packLength URL_LEN = strlen(url);
	
	int totalSize = URL_LEN + filesize + 4;
	
	byte byteArray[totalSize];
	//store [URL_LEN][filesize] in first 4 bytes of packet
	byteArray[0] = URL_LEN & 0x00FF;
	byteArray[1] = (URL_LEN & 0xFF00) >> 8;
	byteArray[2] = filesize & 0x00FF;
	byteArray[3] = (filesize & 0xFF00) >> 8;
		
	//store URL into packet
	for(int i=0;i<URL_LEN;i++){
		byteArray[i+4] = (byte) url[i];
	}
	
	//store file into packet
	char c;
	int count = 4+URL_LEN;
	while((c = getc(chainfile)) != EOF){
		byteArray[count++] = (byte)c;
	}
	fclose(chainfile);
	if(debug)
		printByteArray(totalSize, byteArray, URL_LEN);
	
	  char ssNumString[10];
	  int ssNum;
	  int index = 0;
	  int offset = 4;
	  while((char)byteArray[URL_LEN+offset] == ' ' || (char)byteArray[URL_LEN+offset] == '\t'){
	    ++offset;
	  }
	 while((char)byteArray[URL_LEN+offset] != ' ' && (char)byteArray[URL_LEN+offset] != '\t' 
	    && (char)byteArray[URL_LEN+offset] != '\n' && (char)byteArray[URL_LEN+offset] != '\0' && (URL_LEN+offset)<totalSize){
		  ssNumString[index++] = (char)byteArray[URL_LEN+offset];
		  ++offset;
		  if(index > 9){
			printf("SS Number was too large. Exiting...\n");
			free(firstSS);
			exit(1);
		  }
	  }
	  ssNumString[index] = '\0';
	  
	  if(debug > 1)printf("ss string: %s\n", ssNumString);
	  ssNum = atoi(ssNumString);
	  
	  if(ssNum <0){
		ssNum = 0;
	  }
	  if(ssNum == 0){
		   //get the # of ss left. If 0, then wget...
		char website[URL_LEN+1];
		 for(int i=4;i<URL_LEN+4;i++){
		 //The URL
			website[i-4] = byteArray[i];
		 }
		 website[URL_LEN] = '\0';
	 
		 if(debug > 1)printf("\nThis is the last ss: %c, so WGET the file\n", ssNum);
		 char* fileAddress = (char*)malloc(sizeof(char)*1000);
		 strcpy(fileAddress, website);//File address that we are going to recieve
		 FILE * urlFile = getFile(fileAddress);
		 fclose(urlFile);
		 free(fileAddress);
		 free(firstSS);
	  }
	
	
	printChainList(byteArray, totalSize, URL_LEN);
	
	if(debug > 1)printf("ss num: %d\n",ssNum);
	int randomSS = getRandomNumber(ssNum);
	
	int startIndex = extractIPAddress(firstSS, byteArray, randomSS);
	if(debug > 1)printf("start: %d\n", startIndex);
	extractPort(firstSS, byteArray, startIndex, totalSize);
	
	decrementSSNum(byteArray);
	
	/* calculate new byte array */	
	
	int byteArraySize = totalSize;
	int newFileLength = filesize;
	
	for(int i=0;i<URL_LEN+4;i++){
		if((char)byteArray[i] == ' ')
			byteArraySize--;
	}
	
	for(int i=URL_LEN+4;i<totalSize;i++){
		if((char)byteArray[i] == ' '){
			byteArraySize--;
			newFileLength--;
		}
	}
	
	byte newByteArray[byteArraySize];
	//put the byteArray into a new, smaller byte array without the spaces
	index = 0;
	for(int i=0;i<byteArraySize;i++){
		while((char)byteArray[index] == ' '){++index;}
		if(index >= totalSize) break;
		newByteArray[i] = byteArray[index++];
	}
	newByteArray[2] = newFileLength & 0x00FF;
	newByteArray[3] = (newFileLength & 0xFF00) >> 8;
	
	if(debug){
		printf("The New Byte Array to be passed:\n");
		printByteArray(byteArraySize, newByteArray,URL_LEN);
	}
	sockfd = openTCPsocket(firstSS->ip, port);
	
	
	printf("  Next SS is %s, %s\n",firstSS->ip, port);
	
	/* send byteArray to ss */
	
	if ((bytes_sent = send(sockfd, newByteArray, totalSize, 0)) == -1){// if there was an error listening;
		perror("send struct failed\n");// state the error was in Listen;
		free(firstSS);
		exit(1); //Quit
	}
	else{
		if(debug)printf("Sent request to server %s:%s via TCP\n",firstSS->ip, port);
	}
	
	free(firstSS);
	
	/* Call select() */
	int result;
	bool timeout = true;
	
	do {
	   FD_ZERO(&readset); //set readset
	   FD_SET(sockfd, &readset); //set file descriptor set
	   result = select(sockfd + 1, &readset, NULL, NULL, &tv); //select on first lowest sockfd w/ tv as timeout
	} while (result == -1 && errno == EINTR);

	/*wait for the given timeout period to receive the fileByteArray
		just recieve the first 4 bytes for file length  urlFileSize*/
	int urlFileSize, numbytes;
	byte ul[4];
	if (result > 0) {
	   if (FD_ISSET(sockfd, &readset)) {
			/*receive the returning file size and allocate space for it. */
			
			if( (numbytes = recv(sockfd, ul, 4, 0))){
				urlFileSize = (int)( ((ul[3]<<24)&0xFF000000) + ((ul[2]<<16)&0x00FF0000) + ((ul[1]<<8)&0x0000FF00) + ul[0] );
				if(debug){printf("recieved urlFileSize of %d\n",urlFileSize);}
			}
			else{
				if(debug)printf("data not received");
			}
		  timeout = false;
		  if (result == 0) {
		     /* This means the other side closed the socket */
		     if(debug)printf("socket was closed on server side");
		     close(sockfd);
		  }
		  else if(result == -1){
		  	perror("Client: recv(): ");	
		  	exit(1);
		  }
	   }
	}
	else if (result < 0) {
	   /* An error ocurred, just print it to stdout */
	   if(debug)perror("Error on select():");
	}
	
	int totalByteArraySize = urlFileSize+4;
	byte fileByteArray[totalByteArraySize];
    //timeout
    if(timeout){
	   printf("Server timed out.\n");
	}
    else{
		fileByteArray[0] = ul[0];
		fileByteArray[1] = ul[1];
		fileByteArray[2] = ul[2];
		fileByteArray[3] = ul[3];
		
		receiveFile(sockfd, fileByteArray, urlFileSize);
		printf("  Received file %s\n", "requested_file");
		if(debug)printFileByteArray(fileByteArray);
		saveFileToDisk(fileByteArray, urlFileSize);
	}
	
    close(sockfd);
}

void saveFileToDisk(byte * byteArray, int urlFileSize){
	FILE * newFile = fopen("requested_file", "w");
	if(newFile == NULL){
		printf("problem creating new file requested_file\n");
		exit(1);
	}
	for(int i=4;i<urlFileSize+4;i++){
		fputc(byteArray[i], newFile);
	}
	fclose(newFile);
}

/** receive the file in a byte array from the requested ss */
void receiveFile(int sockfd, byte * byteArray, int urlFileSize){
	int offset = 4;
	int numbytes;
	//receive the URL and file into the byte array
	while( (numbytes = recv(sockfd, byteArray+offset, urlFileSize, 0)) > 0 ) {
		if(debug)printf("received %d/%d bytes\n", numbytes, urlFileSize);
		if(numbytes >= urlFileSize)break;		
		offset += numbytes;
		urlFileSize -= numbytes;
	}
	if(numbytes < 0){
		perror("problem receiving from server:");
	}
	if(debug){
		printf("File received:\n");
		printFileByteArray(byteArray);
	}
}

void printChainList(byte * byteArray, int totalSize, int URL_LEN){
	printf("  chainlist is\n  ");
	int index = URL_LEN +4;
	while((char)byteArray[index] != '\n'){
		++index;
	}
	while(index < totalSize-1){
		printf("%c",byteArray[++index]);
		if((char)byteArray[index+1] == ',' && (char)byteArray[index+2] != ' ')
			printf(" ");
		if((char)byteArray[index] == '\n')
			printf("  ");
	}
	printf("\n");
}

void decrementSSNum(byte * byteArray){
	int ssNumStart = (packLength)byteArray[0]+4;
	int ssNumEnd = ssNumStart;
		
	//find the end location for the ss number
	while((char)byteArray[ssNumEnd] != '\n' && (char)byteArray[ssNumEnd] != ' ' && (char)byteArray[ssNumEnd] != '\t') {
		++ssNumEnd;
	}
	//put the ss Number into a char array
	char ssChars[ssNumEnd-ssNumStart];
	int index = 0;
	for(int i=ssNumStart;i<ssNumEnd;i++){
		ssChars[index++] = byteArray[i];
	}
	ssChars[index] = '\0';
	//decrement ssNum and put back in byteArray
	int num;
	num = atoi(ssChars);
	--num;
	int newlength = sprintf(ssChars,"%d",num);
	ssChars[newlength] = '\0';
	
	index = 0;
	//put in the port number with ' ''s if less digits
	for(int i=ssNumStart;i<ssNumEnd;i++){
		if(i>=ssNumStart+newlength){
			byteArray[i] = ' ';
		}
		else{
			byteArray[i] = ssChars[index++];
		}
	}
}
  
/** extract the Port from the byteArray and store in nextSS->port*/
void extractPort(struct ipPortPair *nextSS, byte * byteArray, int startIndex, int totalSize){
	//find the start and end of the port number
	int endIndex;
	
	while((char)byteArray[startIndex] == ' ' || (char)byteArray[startIndex] == '\t'){
		++startIndex;
	}
	endIndex = startIndex;
	while((char)byteArray[endIndex] != '\n' && (char)byteArray[endIndex] != '\0' && (char)byteArray[endIndex] != ' ' && (char)byteArray[endIndex] != '\t'){
		++endIndex;
	}
	
	//save port number into nextSS->port
	char port[(endIndex-startIndex)];
	int index = 0;
	for(int i=startIndex;i<endIndex;i++){
		port[index++] = byteArray[i];
		byteArray[i] = ' ';
	}
	port[index] = '\0';
	
	while((char)byteArray[endIndex] == ' ' || (char)byteArray[endIndex] == '\t')endIndex++;
	
	if(endIndex < totalSize){
		byteArray[endIndex] = ' ';
	}
	nextSS->port = atoi(port);
}

/** extract the ip address from the byteArray and store in nextSS->ip*/
int extractIPAddress(struct ipPortPair * nextSS, byte * byteArray, int randomSS){
	packLength URL_LEN = (packLength)byteArray[0];
	int startIndex = 4+URL_LEN;
	int endIndex = 4+URL_LEN;

		//extract the next ss location in the array
	for(;randomSS >0;--randomSS){
		while((char)byteArray[startIndex] != '\n'){ 
			++startIndex;
		}
		++startIndex;
	}
	endIndex = startIndex;
	while((char)byteArray[endIndex] != ','){
		++endIndex;
	}

	int index = 0;
	for(int i=startIndex;i<endIndex;i++){
		nextSS->ip[index++] = byteArray[i];
		byteArray[i] = ' ';
	}
	nextSS->ip[index] = '\0';
	byteArray[endIndex] = ' ';
	return startIndex;
}

/**geneartes a random number */
int getRandomNumber(int entries) {
	srand ( time(NULL) );//Seed the random number generator
	int retVal = rand() % entries;
	++retVal;
	return retVal;
}

/**
The Following code Opens a TCP Socket
*/
int openTCPsocket(char * hostname, char * port){
    int sockfd; //socket file descriptor
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN]; //address
	
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(hostname, port, &hints, &servinfo)) != 0) { 
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		freeaddrinfo(servinfo); // all done with this structure
        exit(1);
    }

    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
      //The new socket descriptor to be used in subsequent calls, or -1 on error 
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
		freeaddrinfo(servinfo); // all done with this structure
		freeaddrinfo(p);
        exit(1);
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
            s, sizeof s);

    freeaddrinfo(servinfo); // all done with this structure
	return sockfd;
}

void printByteArray(int totalSize, byte* byteArray, int URL_LEN){
	printf("total size: %d\n", totalSize);
	printf("the byteArray\n");
	printf("+URL size: %d\n",(packLength)byteArray[0]);
	for(int i=4;i<URL_LEN+4;i++){
		printf("%c",(char)byteArray[i]);
	}
	printf("\n+filesize: %d\n------------------\n",(packLength)byteArray[2]);
	for(int i=4+URL_LEN;i<totalSize;i++){
		printf("%c",(char)byteArray[i]);
	}
	printf("\n------------------\n");
}

void printFileByteArray(byte* byteArray){
	int filesize = (int)( ((byteArray[3]<<24)&0xFF000000) + ((byteArray[2]<<16)&0x00FF0000) + ((byteArray[1]<<8)&0x0000FF00) + byteArray[0] );
	int totalSize = filesize + 4;
	printf("the byteArray (%d Bytes)\n", totalSize);
	printf("File size: %d\n",filesize);
	printf("------------------\n");
	for(int i=4;i<filesize+4;i++){
		printf("%c",(char)byteArray[i]);
	}
	printf("\n------------------\n");
}

//fileName was a bad choice for a variable
//This is really the file being requested.
FILE* getFile(char* fileName){
	//Use wget to retrive file.
	//wget by default stores the file to 'requested_file'
	//Use system("wget -O outputFileName fileBeingRequested") to get the file to disk
	
	char *command =(char*) malloc(sizeof(char)*100);
	command[99] = '\0';
	strcpy(command, "wget -O requested_file ");
	strcat(command,fileName);
	
	system(command);
	//hopefully we have the file, cross your fingers...
	FILE* file = fopen("requested_file", "r");
	free(command);
	return file;
}


int main(int argc, char **argv){
  char * url;
  char port[5];
  strcpy(port, "3490");
  
  if(argc > 1) {
    url = argv[1];
  }
  else {
    printf("Correct usage: awget <URL> [-c chainfile]\n");
	exit(1);
  }
  
  printf("awget:\n");
    
  FILE * chainfile;
  char * filename;
  int c;
  int fileProvided = 0;

  
  while((c = getopt(argc, argv, "c:")) != -1){
    
	switch (c){
	  case 'c': 
	    fileProvided = 1;
		filename = optarg;
		break;
	}
  }
  //Since the chainfile argument is optional, if not specified then awget should 
  //read the chain configuration from a local file called chaingang.txt. 
  //If no chainfile is given at the command line and awget fails to locate 
  //the chaingang.txt file, awget should print an error message and exit.
  if(!fileProvided) {
	  filename = new char[14];
	  strcpy(filename,"chaingang.txt"); // no file provided,so open default file 
  }
  
  //open the chain file in read/write mode
  chainfile = fopen(filename, "r+"); 
  //failed to find the file with filename
  if(chainfile == NULL) {
	printf("Unable to locate the default file %s Program will exit.\n", filename);
	free(filename);
	exit(1); //exit failure
  }
  
  if(!fileProvided)
	free(filename);
  printf("Requested: %s\n", url);
  requestFile(port, url, chainfile);
  printf("Goodbye!\n");
}
